/**
 * Copyright (C) 2009 fgrilli <federico.grilli@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package com.google.code.mgnlgroovy.scheduler.manager;

import info.magnolia.cms.beans.config.ContentRepository;
import info.magnolia.cms.core.Content;
import info.magnolia.cms.core.HierarchyManager;
import info.magnolia.cms.core.ItemType;
import info.magnolia.cms.core.NodeData;
import info.magnolia.cms.core.search.Query;
import info.magnolia.cms.core.search.QueryManager;
import info.magnolia.cms.core.search.QueryResult;
import info.magnolia.cms.security.AccessDeniedException;
import info.magnolia.cms.util.ContentUtil;
import info.magnolia.cms.util.ExclusiveWrite;
import info.magnolia.cms.util.NodeDataUtil;
import info.magnolia.content2bean.Content2BeanException;
import info.magnolia.content2bean.Content2BeanUtil;
import info.magnolia.context.MgnlContext;
import com.google.code.mgnlgroovy.scheduler.JobDefinition;
import com.google.code.mgnlgroovy.scheduler.SchedulerConsts;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.jcr.ItemExistsException;
import javax.jcr.ItemNotFoundException;
import javax.jcr.RepositoryException;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * @author fgrilli
 * @version $Id: DefaultJobDefinitionManager.java 39 2009-04-20 16:30:54Z federico.grilli $
 */
public class DefaultJobDefinitionManager implements JobDefinitionManager
{

    protected static final String SCHEDULER_CONFIG_JOBS_XPATH_QUERY = "//scheduler//config//jobs/*[jcr:primaryType='mgnl:contentNode']";

    protected static final Logger log = LoggerFactory.getLogger(DefaultJobDefinitionManager.class);

    @SuppressWarnings("unchecked")
    public Collection<Content> findAllJobDefinitions() throws RepositoryException
    {
        QueryManager qm = MgnlContext.getQueryManager(ContentRepository.CONFIG);
        try
        {
            Query q = qm.createQuery(SCHEDULER_CONFIG_JOBS_XPATH_QUERY, Query.XPATH);
            QueryResult qr = q.execute();
            return qr.getContent("mgnl:contentNode");
        }
        catch (RepositoryException e)
        {
            log.error(e.getMessage());
            throw e;
        }
    }

    public void removeJobDefinition(String uuid) throws RepositoryException, ItemNotFoundException
    {
        Content jobNode = ContentUtil.getContentByUUID(ContentRepository.CONFIG, uuid);
        if (jobNode == null)
        {
            String msg = "could not find node to remove with uuid " + uuid;
            log.error(msg);
            throw new ItemNotFoundException(msg);
        }
        try
        {
            Content parent = jobNode.getParent();
            log.info("Trying to delete job {}...", jobNode.getName());
            jobNode.delete();
            parent.save();
            log.info("Job deleted successfully");
        }
        catch (RepositoryException e)
        {
            log.error(e.getMessage());
            throw e;
        }
    }

    @SuppressWarnings("unchecked")
    public String saveOrUpdateJobDefinition(JobDefinition definition) throws ItemExistsException
    {
        if (definition == null)
        {
            log.warn("JobDefinition is null, returning without doing anything.");
            return null;
        }

        synchronized (ExclusiveWrite.getInstance())
        {
            HierarchyManager hm = MgnlContext.getSystemContext().getHierarchyManager(ContentRepository.CONFIG);

            try
            {
                Content root = hm.getContent(SchedulerConsts.ROOT_PATH_FOR_JOBS);
                Content jobNode = ContentUtil.getOrCreateContent(root, definition.getName(), ItemType.CONTENTNODE);
                NodeDataUtil.getOrCreateAndSet(jobNode, "active", definition.isActive());
                if (StringUtils.isNotBlank(definition.getCatalog()))
                    NodeDataUtil.getOrCreateAndSet(jobNode, "catalog", definition.getCatalog());
                if (StringUtils.isNotBlank(definition.getCommand()))
                    NodeDataUtil.getOrCreateAndSet(jobNode, "command", definition.getCommand());
                if (StringUtils.isNotBlank(definition.getGroovyScript()))
                    NodeDataUtil.getOrCreateAndSet(jobNode, "groovyScript", definition.getGroovyScript());
                NodeDataUtil.getOrCreateAndSet(jobNode, "cron", definition.getCron());

                if (definition.getStartTime() != 0)
                    NodeDataUtil.getOrCreateAndSet(jobNode, "startTime", definition.getStartTime());

                if (definition.getEndTime() != 0)
                    NodeDataUtil.getOrCreateAndSet(jobNode, "endTime", definition.getEndTime());

                if (definition.getLastFireTime() != 0)
                    NodeDataUtil.getOrCreateAndSet(jobNode, "lastFireTime", definition.getLastFireTime());

                if (definition.getNextFireTime() != 0
                    && (definition.getEndTime() == 0 || (definition.getEndTime() != 0 && definition.getNextFireTime() < definition
                        .getEndTime())))
                    NodeDataUtil.getOrCreateAndSet(jobNode, "nextFireTime", definition.getNextFireTime());

                NodeDataUtil.getOrCreateAndSet(jobNode, "terminatedWithError", definition.isTerminatedWithError());

                if (StringUtils.isNotBlank(definition.getDescription()))
                    NodeDataUtil.getOrCreateAndSet(jobNode, "description", definition.getDescription());

                if (definition.getParams() != null && !definition.getParams().isEmpty())
                {
                    Content paramNode = ContentUtil.getOrCreateContent(jobNode, "params", ItemType.CONTENTNODE);
                    Iterator children = paramNode.getNodeDataCollection().iterator();
                    while (children.hasNext())
                    {
                        NodeData node = (NodeData) children.next();
                        log.debug("deleting node {}", node.getHandle());
                        node.delete();
                    }
                    Iterator it = definition.getParams().entrySet().iterator();
                    while (it.hasNext())
                    {
                        Map.Entry pairs = (Map.Entry) it.next();
                        NodeDataUtil.getOrCreateAndSet(paramNode, (String) pairs.getKey(), pairs.getValue());
                    }
                }
                root.save();

                jobNode = ContentUtil.getOrCreateContent(root, definition.getName(), ItemType.CONTENTNODE);
                log.debug("saved job UUID is {}", jobNode.getUUID());
                return jobNode.getUUID();
            }
            catch (AccessDeniedException e)
            {
                log.error(e.getMessage());
                throw new RuntimeException("can't add job: ", e);
            }
            catch (RepositoryException e)
            {
                log.error(e.getMessage());
                throw new RuntimeException("can't add job: ", e);
            }
        }
    }

    protected List<JobDefinition> node2JobDefinitionList(Collection<Content> results, boolean recursive,
        Class<JobDefinition> clazz)
    {
        List<JobDefinition> list = new ArrayList<JobDefinition>();
        long start = System.currentTimeMillis();
        for (Content node : results)
        {
            try
            {
                JobDefinition jd = clazz.cast(Content2BeanUtil.toBean(node, true, clazz));
                list.add(jd);
            }
            catch (Content2BeanException e)
            {
                log.error(e.getMessage());
                throw new RuntimeException(e);
            }
        }
        long stop = System.currentTimeMillis();
        log.debug("Content2Bean on result set took {} ms", stop - start);
        return list;
    }

    public JobDefinition getJobDefinitionByName(String jobName) throws RepositoryException
    {
        HierarchyManager hm = MgnlContext.getSystemContext().getHierarchyManager(ContentRepository.CONFIG);
        Content root = hm.getContent(SchedulerConsts.ROOT_PATH_FOR_JOBS);

        Content jobNode = ContentUtil.getOrCreateContent(root, jobName, ItemType.CONTENTNODE);
        try
        {
            JobDefinition retVal = (JobDefinition) Content2BeanUtil.toBean(jobNode, true, JobDefinition.class);
            if (retVal != null)
                return retVal;
            throw new ItemNotFoundException();
        }
        catch (Content2BeanException e)
        {
            log.error(e.getMessage());
            throw new RuntimeException(e);
        }
    }

    public JobDefinition getJobDefinitionByUUID(String uuid) throws RepositoryException
    {
        Content jobNode = ContentUtil.getContentByUUID(ContentRepository.CONFIG, uuid);
        try
        {
            JobDefinition retVal = (JobDefinition) Content2BeanUtil.toBean(jobNode, true, JobDefinition.class);
            if (retVal != null)
                return retVal;
            throw new ItemNotFoundException();
        }
        catch (Content2BeanException e)
        {
            log.error(e.getMessage());
            throw new RuntimeException(e);
        }
    }

    public boolean sameJobNameExists(String jobName)
    {
        HierarchyManager hm = MgnlContext.getSystemContext().getHierarchyManager(ContentRepository.CONFIG);
        return hm.isExist(SchedulerConsts.ROOT_PATH_FOR_JOBS + "/" + jobName);
    }
}
